package com.tian.service.impl;

import com.alibaba.fastjson.JSON;
import com.tian.config.RedisConfig;
import com.tian.dto.*;
import com.tian.entity.UserPoint;
import com.tian.entity.UserPointRecord;
import com.tian.enums.FlagEnum;
import com.tian.enums.ResultCode;
import com.tian.enums.StatusEnums;
import com.tian.enums.UserPointRecordTypeEnum;
import com.tian.mapper.UserPointMapper;
import com.tian.mapper.UserPointRecordMapper;
import com.tian.service.UserPointService;
import com.tian.util.*;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author tianwc  公众号：java后端技术全栈、面试专栏
 * @version 1.0.0
 * @date 2023年06月05日 16:41
 * 博客地址：<a href="http://woaijava.cc/">博客地址</a>
 * <p>
 * 用户注册完成后  采用MQ 形式 异步完成积分账户初始化完成
 */
@Slf4j
@Service
public class UserPointServiceImpl implements UserPointService {

    @Resource
    private UserPointMapper userPointMapper;
    @Resource
    private UserPointRecordMapper userPointRecordMapper;
    @Resource
    private RedisConfig redisConfig;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ThreadDelayTaskService threadDelayTaskService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public CommonResult<Boolean> add(UserPointDto userPointDto) {
        int flag = userPointMapper.insert(userPointDto);
        if (flag == FlagEnum.SUCCESS.getFlag()) {
            addUserPointRecord(userPointDto.getPoints(), UserPointRecordTypeEnum.ADD.getType(), userPointDto.getUserId(),
                    StatusEnums.SUCCESS.getStatus(), new Date(), "USER_REGISTER_" + userPointDto.getUserId());
            redisConfig.set(RedisConstantPre.USER_POINT_PRE + userPointDto.getUserId(), JSON.toJSONString(userPointDto));
            return CommonResult.success(Boolean.TRUE);
        }
        return CommonResult.failed("新增失败");
    }

    @Override
    public CommonResult<UserPointDto> queryByUserId(Long userId) {
        String key = RedisConstantPre.USER_POINT_PRE + userId;
        String cache = redisConfig.get(key);
        if (StringUtil.isBlank(cache)) {
            log.error("查询我的积分失败，参数userId={} 有误", userId);
            return CommonResult.failed(ResultCode.PARAMETER_ERROR);
        }
        UserPointDto userPointDto = JSON.parseObject(cache, UserPointDto.class);
        if (userPointDto.getPoints() == null) {
            log.error("查询我的积分失败，参数userId={} 有误", userId);
            return CommonResult.failed(ResultCode.PARAMETER_ERROR);
        }
        return CommonResult.success(userPointDto);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public CommonResult<Boolean> update(UserPointUpdateDto userPointUpdateDto) {
        String key = RedisConstantPre.USER_POINT_LOCK_PRE + userPointUpdateDto.getUserId();
        RLock rLock = redissonClient.getLock(key);
        try {
            rLock.lock();
            UserPoint userPoint = userPointMapper.selectByUserId(userPointUpdateDto.getUserId());
            if (userPoint == null) {
                log.error("修改积分失败，参数userId={} 有误", userPointUpdateDto.getUserId());
                return CommonResult.failed(ResultCode.PARAMETER_ERROR);
            }
            UserPointRecord userPointRecord = userPointRecordMapper.selectByOrderNo(userPointUpdateDto.getOrderNo());
            if (userPointRecord != null) {
                return CommonResult.success(Boolean.TRUE);
            }
            int point = userPoint.getPoints();
            if (userPointUpdateDto.getType() == UserPointRecordTypeEnum.DELETE.getType()) {
                if (point < userPointUpdateDto.getPoints()) {
                    log.error("积分扣减失败，参数userId={} 不足够", userPointUpdateDto.getUserId());
                    return CommonResult.failed(ResultCode.ACTIVITY_NO_ENOUGH);
                }
                redisConfig.delete(RedisConstantPre.USER_POINT_PRE + userPointUpdateDto.getUserId());
                userPoint.setPoints(point - userPointUpdateDto.getPoints());

            } else {
                redisConfig.delete(RedisConstantPre.USER_POINT_PRE + userPointUpdateDto.getUserId());
                userPoint.setPoints(point + userPointUpdateDto.getPoints());
            }
            int flag = userPointMapper.updateByPrimaryKey(userPoint);
            if (flag == FlagEnum.SUCCESS.getFlag()) {
                addUserPointRecord(userPointUpdateDto.getPoints(), userPointUpdateDto.getType(), userPointUpdateDto.getUserId(),
                        StatusEnums.SUCCESS.getStatus(), new Date(), userPointUpdateDto.getOrderNo());
                //缓存一致性   双删
                threadDelayTaskService.delayDeleteCache(RedisConstantPre.USER_POINT_PRE + userPointUpdateDto.getUserId(), ThreadDelayTaskService.DELAY_TIME);
                redisConfig.set(RedisConstantPre.USER_POINT_PRE + userPointUpdateDto, JSON.toJSONString(userPoint));
                return CommonResult.success(Boolean.TRUE);
            }
        } finally {
            rLock.unlock();
        }
        return CommonResult.failed("变更积分失败");
    }

    private void addUserPointRecord(int point, int type, Long userId, int status, Date createTime, String orderNo) {
        UserPointRecord record = new UserPointRecordDto();
        record.setPoint(point);
        record.setUserId(userId);
        record.setStatus(status);
        record.setCreateTime(createTime);
        record.setType(type);
        record.setOrderNo(orderNo);
        record.setUpdateTime(createTime);
        userPointRecordMapper.insert(record);
    }

    @Override
    public CommonResult<Boolean> userPointRank(Integer rankCount) {
        List<UserPoint> userPointList = userPointMapper.selectByRankCount(rankCount);
        Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
        for (UserPoint userPoint : userPointList) {
            // 排行分数=先按照真实分数进行排名，如果分数一样，再按照时间戳来排名，谁先达到谁在前
            //分数=贡献值 + (Integer.MAX-时间戳) * 10^-13
            double point = RandomScore.calculateScore(userPoint.getPoints(), userPoint.getCreateTime().getTime());

            DefaultTypedTuple<String> tuple = new DefaultTypedTuple<>(userPoint.getUserId().toString(), point);
            tuples.add(tuple);
        }
        String key = getRankKey();
        redisTemplate.delete(key);
        redisTemplate.opsForZSet().add(key, tuples);
        //设置有效期为2天
        redisTemplate.expire(key, 172800, TimeUnit.SECONDS);
        return CommonResult.success();
    }

    @Override
    public CommonResult<Long> getUserPointRank(Long userId) {
        String key = getRankKey();
        Long rankNum = redisTemplate.opsForZSet().reverseRank(key, userId.toString());
        if (rankNum == null) {
            return CommonResult.success(-1L);
        }
        return CommonResult.success(rankNum);
    }

    @Override
    public CommonResult<UserRankResDto> rankList(Integer rankCount) {
        String key = getRankKey();
        Set<ZSetOperations.TypedTuple<String>> rangeWithScores = redisTemplate.opsForZSet().reverseRangeWithScores(key, 0, rankCount);
        log.info("获取到的排行和分数列表:" + JSON.toJSONString(rangeWithScores));
        Iterator<ZSetOperations.TypedTuple<String>> iterator = rangeWithScores.iterator();
        List<UserRankInfoResDto> list = new ArrayList<>();
        int i = 1;
        while (iterator.hasNext()) {
            UserRankInfoResDto userRankInfoResDto = new UserRankInfoResDto();
            ZSetOperations.TypedTuple<String> typedTuple = iterator.next();
            userRankInfoResDto.setRank(i);
            userRankInfoResDto.setUserId(Long.parseLong(typedTuple.getValue()));
            list.add(userRankInfoResDto);
            i++;
        }
        UserRankResDto userRankResDto = new UserRankResDto();
        userRankResDto.setList(list);

        //
        return CommonResult.success(userRankResDto);
    }

    private static String getRankKey() {
        return "user-point-rank" + DateUtil.getYesterday();
    }
}
